package com.hw.kms.keyfile;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;

import com.crb.gp.util.GpUtil;
import com.crb.kms.hsm.codec.Constants;
import com.crb.kms.hsm.codec.HsmSJJ1312BUtil;
import com.crb.kms.hsm.sjj1312b.codec.response.HsmEncOrDecryUsingSmkResponse;
import com.crb.kms.hsm.sjj1312b.codec.response.HsmMacCalculateResponse;
import com.crb.kms.hsm.sjj1312b.codec.response.HsmResponse;
import com.crb.util.CrbUtil;

public class MKeyFileGenUtil {
	/**
	 * 生成卡片生产主密钥文件(CPMK)
	 * 
	 * @param mkIndex
	 *            卡片生产种子密钥索引号
	 * @param tpkIndex
	 *            芯片厂家数据传递完整性验证密钥索引号
	 * @param kekIndex
	 *            芯片厂家数据传递敏感数据保护密钥索引号
	 * @param seVendor
	 *            芯片厂家标识
	 * @param keyId
	 *            密钥标识
	 * @param 文件存放路径
	 * @return
	 * @throws KmsException
	 */
	public static boolean exportCardCPMKFile(int mkIndex, int tpkIndex, int kekIndex,
			String seVendor, String keyId, String filePath) throws KmsException {
		try {
			// String cmkId = genCMKID("00", "0", 1);
			byte[] cpmk = KeyDisperse.genCPMK(mkIndex, seVendor, keyId);

			/*
			 * 生成KCV并加密CMK
			 */
			String[] cpmks = cmkEnc(cpmk, kekIndex);

			/*
			 * 生成文件名称
			 */
			String fileName = genFileName(seVendor, keyId, true);
			filePath += "\\" + fileName;
			/*
			 * 生成文件内容
			 */
			String fileContent = fileContent(true, keyId, cpmks[0], cpmks[1], tpkIndex);
			return genFile(filePath, fileContent);
		} catch (Exception e) {
			throw new KmsException("exportCardKeyFile", e);
		}
	}

	/**
	 * 生成CTK密钥文件
	 * 
	 * @param D
	 *            私钥
	 * @param M
	 *            公钥
	 * @param signData
	 *            待签名数据
	 * @param tpkIndex
	 *            芯片厂家数据传递完整性验证密钥索引号
	 * @param kekIndex
	 *            芯片厂家数据传递敏感数据保护密钥索引号
	 * @param seVendor
	 *            芯片厂家编码
	 * @param keyId
	 *            密钥标识
	 * @param filePath
	 *            文件存放路径
	 * @return
	 * @throws KmsException
	 */
	public static boolean exportCardCTKFile(String D, String M, int tpkIndex, int kekIndex,
			String seVendor, String keyId, String filePath) throws KmsException {
		try {

			/*
			 * 生成KCV并采用私钥进行签名
			 */
			String[] ctks = tpkSign(M, D, kekIndex);

			/*
			 * 生成文件名称
			 */
			String fileName = genFileName(seVendor, keyId, false);
			filePath += "\\" + fileName;
			/*
			 * 生成文件内容
			 */
			String fileContent = fileContent(false, keyId, ctks[0], ctks[1], tpkIndex);
			return genFile(filePath, fileContent);
		} catch (Exception e) {
			throw new KmsException("exportCardCTKFile", e);
		}
	}

	/**
	 * 对CPMK进行KCV计算并用KEK密钥进行保护
	 * 
	 * @param cpmk
	 *            卡商制卡主密钥
	 * @param ckekIndex
	 *            卡商对应的密钥保护密钥索引号
	 * @return
	 * @throws Exception
	 */
	private static String[] cmkEnc(byte[] cpmk, int ckekIndex) throws Exception {
		HsmEncOrDecryUsingSmkResponse encryKey = HsmSJJ1312BUtil.encOrDecryUsingSmk(ckekIndex, 0,
				false, null, true, 3, null, CrbUtil.ba2HexString(cpmk), null);
		if (encryKey.getStatus() != HsmResponse.RLT_SUCCESS) {
			throw new KmsException("采用指定KEK加密MK异常!");
		}
		String[] rlt = new String[2];
		rlt[0] = encryKey.getData();
		rlt[1] = CrbUtil.ba2HexString(GpUtil.keyCheckValue(cpmk, 4, true));
		return rlt;
	}

	/**
	 * 生成文件名
	 * 
	 * @param proCode
	 *            省份代码
	 * @param cardVendor
	 *            卡商编码
	 * @param keyId
	 *            密钥标识
	 * @param cmk
	 *            是否为CPMK<br>
	 *            <li>true: 是</li><br>
	 *            <li>false: 否</li>
	 * @return
	 */
	private static String genFileName(String seVendor, String keyId, boolean mk) {
		return mk ? ("OP_TSM_MK_" + seVendor + "_" + keyId + ".lsmk") : ("OP_TSM_TK_" + seVendor
				+ "_" + keyId + ".lstk");
	}

	/**
	 * 生成文件内容
	 * 
	 * @param cmk
	 *            是生生成CPMK操作
	 * @param keyId
	 *            密钥标识
	 * @param key
	 *            加密后的CPMK或CTK
	 * @param kcv
	 *            密钥校验值或DS(TPK)
	 * @param ctpkIndex
	 *            卡商数据传递完整性验证密钥索引号
	 * @return
	 * @throws KmsException
	 */
	private static String fileContent(boolean cmk, String keyId, String key, String kcv,
			int ctpkIndex) throws KmsException {
		StringBuilder sb = new StringBuilder(cmk ? "6c736D6B" : "6c73746B");
		sb.append("0100");
		sb.append(CrbUtil.getRandomHexString(8));
		sb.append(keyId);
		sb.append(CrbUtil.long2HexString(key.length() >> 1, 2));
		sb.append(key);
		sb.append(CrbUtil.long2HexString(kcv.length() >> 1, 2));
		sb.append(kcv);

		HsmMacCalculateResponse resp = HsmSJJ1312BUtil.macCalculate(1, 3, ctpkIndex, 0x10, 0, null,
				0, 0, 0, null, 2, "0000000000000000", sb.toString());
		if (resp.getStatus() != HsmResponse.RLT_SUCCESS) {
			throw new KmsException("采用KEK计算MAC异常.");
		}
		sb.append(resp.getMac());
		return sb.toString();
	}

	/**
	 * 用敏感数据保护密钥对钥进行加密并采用私钥对相关数据进行签名
	 * 
	 * @param M
	 *            公钥
	 * @param D
	 *            私钥
	 * @param ckekIndex
	 *            卡商数据传递敏感数据保护密钥索引号
	 * @return
	 * @throws Exception
	 */
	private static String[] tpkSign(String M, String D, int ckekIndex) throws Exception {
		String[] rlt = cmkEnc(CrbUtil.hexString2Ba(M), ckekIndex);
		rlt[1] = sign(M, D);
		return rlt;
	}

	/**
	 * 采用私钥对数据进行签名
	 * 
	 * @param M
	 *            公钥
	 * @param D
	 *            私钥
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws InvalidKeyException
	 * @throws SignatureException
	 */
	public static String sign(String M, String D) throws NoSuchAlgorithmException,
			InvalidKeySpecException, InvalidKeyException, SignatureException {
		byte[] dataBa = CrbUtil.hexString2Ba("76BC87BAECA7725C948FD1C53766454FDA0867AF");
		Signature sig = Signature.getInstance("SHA1WithRSA");

		// 得到私钥
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		PrivateKey privateKey = keyFactory.generatePrivate(new RSAPrivateKeySpec(new BigInteger(M,
				16), new BigInteger(D, 16)));

		// 用私钥来初始化数字签名对象
		sig.initSign(privateKey);
		// 对msgBytes实施签名
		sig.update(dataBa);
		// 完成签名，将结果放入字节数组signatureBytes
		byte[] sigsignatureBytes = sig.sign();
		String signature = CrbUtil.ba2HexString(sigsignatureBytes);
		return signature;
	}

	/**
	 * 生成文件
	 * 
	 * @param path
	 *            文件路径
	 * @param content
	 *            文件内容
	 * @return
	 * @throws IOException
	 */
	private static boolean genFile(String path, String content) throws IOException {
		File file = new File(path);
		File parent = new File(file.getParent());
		if (!parent.exists()) {
			parent.mkdirs();
		}
		if (!file.exists()) {
			file.createNewFile();
		}

		OutputStream os = new FileOutputStream(file);
		os.write(CrbUtil.hexString2Ba(content));
		os.close();
		return true;
	}

	/**
	 * 解析密钥文件
	 * 
	 * @param data
	 *            文件数据字节
	 * @return
	 */
	public static KeyFileInfo decoderKeyFile(byte[] data) {
		KeyFileInfo keyInfo = new KeyFileInfo();
		String d = CrbUtil.ba2HexString(data);

		int keyLen = 0;
		int kcvLen = 0;
		keyInfo.setMagic(d.substring(0, 8));
		keyInfo.setMajorVersion(Integer.parseInt(d.substring(8, 10), 16));
		keyInfo.setMinorVersion(Integer.parseInt(d.substring(10, 12), 16));
		keyInfo.setRand(d.substring(12, 28));
		keyInfo.setKeyId(d.substring(28, 36));

		keyLen = Integer.parseInt(d.substring(36, 40), 16) * 2;
		keyInfo.setKey(d.substring(40, 40 + keyLen));

		kcvLen = Integer.parseInt(d.substring(40 + keyLen, 44 + keyLen)) * 2;
		keyInfo.setKcvOrDs(d.substring(44 + keyLen, 44 + keyLen + kcvLen));

		keyInfo.setMac(d.substring(44 + keyLen + kcvLen));
		keyInfo.setCalcuMacData(d.substring(0, d.length() - 16));
		return keyInfo;
	}

	public static void main(String[] args) throws IOException {
		Constants.HSM_SERVER_IP = "192.168.10.205";
		Constants.HSM_SERVER_PORT = 6666;

		/*
		 * 导出密钥文件，其中的KeyID为密钥标识，建议为4个字节16进制串，要不文件格式将有问题
		 */
		System.out.println(MKeyFileGenUtil.exportCardCPMKFile(10, 10, 10, "0001", "00000002",
				"d:\\"));

		/*
		 * 导出TOKEN密钥文件，其中的KeyID为TOKEN密钥标识，建议为4个字节16进制串，要不文件格式将有问题
		 */
		System.out
				.println(MKeyFileGenUtil
						.exportCardCTKFile(
								"3D381581773056B4705E3CF7FD99DC23895A1DAA01932B41233F5D915ED14F42A3F2AA6AD172F0988E5727121D99A49C3728D0E02E7CE465101588C00E7705C36AA087885904502D006801DF396D58E1F5CFC3901A14922DCE2DC6C6B43BD6477E021A63B87A4F37E1AFE51578157AE8FE1C8FE4825266D469319CE4C3045EE1",
								"BF513967D4F45D7805FD67693F12CEEE6D6C1BBC8EB193F805C49BEBCA13885E159F78074620D0593F3E1DADE6D355FFBA874837F35DC67A7E862B357EC30D3551436C693578D647EB0F0CBE430506923DC3F678D3F35C4A8B7646382CADDEFE65D58257C6AA20CF591C980CD71F594756EFD24A322A8EFFCAA4F997B940F3AD",
								10, 10, "0001", "00000003", "d:\\"));

		/*
		 * 导入密钥，验证MAC，其计算数据通过KeyFileInfo.getCalcuMacData()得到
		 */
		File file = new File("d:\\OP_TSM_MK_0001_00000002.lsmk");
		InputStream is = new FileInputStream(file);

		byte[] ba = new byte[1024];
		int len = is.read(ba);
		is.close();
		byte[] data = new byte[len];
		System.arraycopy(ba, 0, data, 0, len);
		System.out.println(decoderKeyFile(data));
	}
}
